#include "sbrush_sms_synthesizer.h"
#include <iostream>

using namespace sbrush;
using namespace dag;
using namespace std;

SynthNotInitialized::SynthNotInitialized()
:	SmsError("synth not initialized")
{
}

SynthNotInitialized::~SynthNotInitialized() throw() {
}

NoSmsFrame::NoSmsFrame()
:	SmsError("no sms frame")
{
}

NoSmsFrame::~NoSmsFrame() throw() {
}

SmsSynthesizer::SmsSynthesizer() 
:	m_CurrentFrame(NULL),
	m_InternalBuffer(NULL),
	m_InternalBufferSize(0),
	m_Initialized(false),
	m_SamplesInBuffer(0)
{
	/* initalize sms synthesis parameter values. still need to call 
	 * sms_initSynth() before synthesizing audio */
	sms_initSynthParams(&m_SynthParams);	
}

SmsSynthesizer::~SmsSynthesizer() throw() {
	if(m_Initialized) {
		sms_freeSynth(&m_SynthParams);
	}
	delete[] m_InternalBuffer;
}

void SmsSynthesizer::addAudio(float** buffer, 
							  size_t numChannels, 
							  size_t numFrames) {
	ScopedLock sl(m_AudioMutex);
	if (!m_Initialized || m_InternalBuffer == NULL) {
		throw SynthNotInitialized();
	}
	if (m_CurrentFrame == NULL) {
		throw NoSmsFrame();
	}
	
	size_t putPos = addAudioFromInternal(buffer, numChannels, numFrames, 0);
	
	/* synthesize another frame if we didn't fill the buffer yet */
	while (putPos < numFrames) {
		sms_synthesize(const_cast<SMS_Data*>(m_CurrentFrame),
					   m_InternalBuffer,
					   &m_SynthParams);
		m_SamplesInBuffer = m_InternalBufferSize;
		putPos = addAudioFromInternal(buffer, numChannels, numFrames, putPos);
	}
}

void SmsSynthesizer::init(const SMS_Header& header,
						  int sampleRate) {
	ScopedLock sl(m_AudioMutex);
	/* free old synth if this has been called before */
	if(m_Initialized){
		sms_freeSynth(&m_SynthParams);
		m_Initialized = false;
	}
	/* this could be a bit dangerous as there is a big text field in the header
	 * and we'll just be grabbing a pointer to the memory rather than making a
	 * local copy.  Fortunately, we don't need to use this text field so we
	 * should be safe */
	m_Header = header;
	int err = sms_initSynth(const_cast<SMS_Header*>(&m_Header), &m_SynthParams);
	if (err != SMS_OK) {
		throw LibSmsError(err);
	}
	/* TODO: for some reason calling this sets the sizeHop = 0.  Dunno why? */
//	err = sms_changeSynthHop(&m_SynthParams, m_InternalBufferSize);
//	if (err != SMS_OK) {
//		throw LibSmsError(err);
//	}
	m_InternalBufferSize = 0;
	delete[] m_InternalBuffer;
	m_InternalBufferSize = m_SynthParams.sizeHop;
	m_InternalBuffer = new float[m_InternalBufferSize];
	m_SamplesInBuffer = 0;
	m_Initialized = true;
}

size_t SmsSynthesizer::samplesPerSmsFrame() const {
	return m_InternalBufferSize;
}

const SMS_Header& SmsSynthesizer::header() const {
	return m_Header;
}

bool SmsSynthesizer::isReady() const {
	return m_Initialized;
}

void SmsSynthesizer::setFrame(const SMS_Data& synthData) {
	ScopedLock sl(m_AudioMutex);
	m_CurrentFrame = &synthData;
}


size_t SmsSynthesizer::addAudioFromInternal(float** buffer, 
										  size_t numChannels, 
										  size_t numFrames, 
										  size_t bufferOffset) {
	if (m_SamplesInBuffer > 0) {
		size_t getPos = m_InternalBufferSize - m_SamplesInBuffer;
		size_t num2Put = 0;
		size_t numLeft = numFrames - bufferOffset;
		if (m_SamplesInBuffer > numLeft) {
			num2Put = numLeft;
		} else {
			num2Put = m_SamplesInBuffer;
		}
		for (size_t channel = 0; channel < numChannels; channel++) {
			memcpy(&buffer[channel][bufferOffset],
				   &m_InternalBuffer[getPos], 
				   num2Put * sizeof(float));
		}
		bufferOffset += num2Put;
		m_SamplesInBuffer -= num2Put;
	}
	
	return bufferOffset;
}